/* -*- c++ -*- */
#ifndef _OGL_ARRAYS_H_
#define _OGL_ARRAYS_H_

#include <stdlib.h>
#include <assert.h>
#ifndef __MACH__
#include <malloc.h>
#else // __MACH__
#endif // __MACH__

#ifndef GL_GLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES
#endif

#ifdef USE_GID_ELEM
#define GID
#endif // USE_GID_ELEM

#ifdef GID

#include "gidOGLext.h"

#else // GID

#ifdef _WIN32
#include <windows.h>
#include <windef.h>
#include <wingdi.h> // for wglGetProcAddress
#endif // _WIN32

/* OpenGL 1.2: */
#ifndef GL_MAX_ELEMENTS_VERTICES
#define GL_MAX_ELEMENTS_VERTICES          0x80E8
#endif // GL_MAX_ELEMENTS_VERTICES

#ifndef GL_MAX_ELEMENTS_INDICES
#define GL_MAX_ELEMENTS_INDICES           0x80E9
#endif // GL_MAX_ELEMENTS_INDICES

int OpenGLExtensionPresent( const char *ext);
int CheckGLError( const char *txt);
float getOGLVersion();
bool OpenGLVertexArrayAvailable();

// #ifndef WINGDIAPI
// #define WINGDIAPI
// #endif
// 
// #if (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED) || defined(MINGW)
// #define CALLBACK    __stdcall
// #define WINAPI      __stdcall
// #define WINAPIV     __cdecl
// #define APIENTRY    WINAPI
// #define APIPRIVATE  __stdcall
// #define PASCAL      __stdcall
// #else
// #define CALLBACK
// #define WINAPI
// #define WINAPIV
// #define APIENTRY    WINAPI
// #define APIPRIVATE
// #define PASCAL      pascal
// #endif

void ( *_gidOGLGetProcAddress( const char *))();
#ifndef _WIN32
#ifndef __MACH__
#define gidOGLGetProcAddress(CAST,STR)   ( CAST)glXGetProcAddress( ( const GLubyte *)( STR));
#else
#define gidOGLGetProcAddress(CAST,STR)   ( NULL)
#endif /* __APPLE_ */
#else /* !_WIN32 */
#define gidOGLGetProcAddress(CAST,STR)   ( CAST)_gidOGLGetProcAddress( ( STR));
#endif /* !_WIN32 */

#endif // GID

#include <GL/gl.h>

#ifdef _WIN32
/* OpenGL 1.5 */

#ifndef GL_VERSION_1_5
/* GL types for handling large vertex buffer objects */
typedef ptrdiff_t GLintptr;
typedef ptrdiff_t GLsizeiptr;
#endif

#ifndef GL_ARB_vertex_buffer_object
#define GL_ARB_vertex_buffer_object 1

#define GL_BUFFER_SIZE                    0x8764
#define GL_BUFFER_USAGE                   0x8765
#define GL_QUERY_COUNTER_BITS             0x8864
#define GL_CURRENT_QUERY                  0x8865
#define GL_QUERY_RESULT                   0x8866
#define GL_QUERY_RESULT_AVAILABLE         0x8867
#define GL_ARRAY_BUFFER                   0x8892
#define GL_ELEMENT_ARRAY_BUFFER           0x8893
#define GL_ARRAY_BUFFER_BINDING           0x8894
#define GL_ELEMENT_ARRAY_BUFFER_BINDING   0x8895
#define GL_VERTEX_ARRAY_BUFFER_BINDING    0x8896
#define GL_NORMAL_ARRAY_BUFFER_BINDING    0x8897
#define GL_COLOR_ARRAY_BUFFER_BINDING     0x8898
#define GL_INDEX_ARRAY_BUFFER_BINDING     0x8899
#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A
#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B
#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C
#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D
#define GL_WEIGHT_ARRAY_BUFFER_BINDING    0x889E
#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F
#define GL_READ_ONLY                      0x88B8
#define GL_WRITE_ONLY                     0x88B9
#define GL_READ_WRITE                     0x88BA
#define GL_BUFFER_ACCESS                  0x88BB
#define GL_BUFFER_MAPPED                  0x88BC
#define GL_BUFFER_MAP_POINTER             0x88BD
#define GL_STREAM_DRAW                    0x88E0
#define GL_STREAM_READ                    0x88E1
#define GL_STREAM_COPY                    0x88E2
#define GL_STATIC_DRAW                    0x88E4
#define GL_STATIC_READ                    0x88E5
#define GL_STATIC_COPY                    0x88E6
#define GL_DYNAMIC_DRAW                   0x88E8
#define GL_DYNAMIC_READ                   0x88E9
#define GL_DYNAMIC_COPY                   0x88EA

typedef void ( __stdcall *PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer);
typedef void ( __stdcall *PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers);
typedef void ( __stdcall *PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers);
typedef GLboolean ( __stdcall *PFNGLISBUFFERARBPROC) (GLuint buffer);
typedef void ( __stdcall *PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);
typedef void ( __stdcall *PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data);
typedef void ( __stdcall *PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data);
typedef GLvoid* ( __stdcall *PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access);
typedef GLboolean ( __stdcall *PFNGLUNMAPBUFFERARBPROC) (GLenum target);
typedef void ( __stdcall *PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint *params);
typedef void ( __stdcall *PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, GLvoid* *params);

extern PFNGLBINDBUFFERARBPROC glBindBuffer;
extern PFNGLDELETEBUFFERSARBPROC glDeleteBuffers;
extern PFNGLGENBUFFERSARBPROC glGenBuffers;
extern PFNGLISBUFFERARBPROC glIsBuffer;
extern PFNGLBUFFERDATAARBPROC glBufferData;
extern PFNGLBUFFERSUBDATAARBPROC glBufferSubData;
extern PFNGLGETBUFFERSUBDATAARBPROC glGetBufferSubData;
extern PFNGLMAPBUFFERARBPROC glMapBuffer;
extern PFNGLUNMAPBUFFERARBPROC glUnmapBuffer;
extern PFNGLGETBUFFERPARAMETERIVARBPROC glGetBufferParameteriv;
extern PFNGLGETBUFFERPOINTERVARBPROC glGetBufferPointerv;

#endif // GL_ARB_vertex_buffer_object

int initVertexBufferObjectFuncs();

#endif // _WIN32

#define OGLARRAY_POINTS       0x001
#define OGLARRAY_LINES        0x002
#define OGLARRAY_TRIANGLES    0x004
#define OGLARRAY_QUADS        0x008

typedef struct {
  GLfloat x, y, z;
} _t_ogl_vector;

typedef struct {
  GLfloat x, y, z;
  GLfloat nx, ny, nz;
  GLfloat s, t;
} _t_ogl_vertex_info; // 32 bytes;

// allocs en, como minimo 8-bytes boundary
// para NVIDIA/ATI recomiendan 32-bytes o 64-bytes por vertice
#define VA_ALIGNMENT     32

typedef enum {
  OGLARRAY_DISPLAY_LIST = 0,
  OGLARRAY_VERTEX_ARRAY,
  OGLARRAY_BUFFER_OBJECT
} _t_ogl_render_method;

inline const char *str_oglarray_render_method( _t_ogl_render_method rm) {
  const char *ret = "N/A";
  switch( rm) {
    case OGLARRAY_DISPLAY_LIST:  ret = "display list"; break;
    case OGLARRAY_VERTEX_ARRAY:  ret = "vertex array"; break;
    case OGLARRAY_BUFFER_OBJECT: ret = "buffer object"; break;
  }
  return ret;
}

class OglArray {
 public: // _init( GL_FALSE), _hayVBO( GL_FALSE), 
  OglArray(): _render_method( OGLARRAY_VERTEX_ARRAY),
              _lst_vertices( NULL), _lst_normals( NULL), _lst_tex_coords( NULL), _lst_colors( NULL),
              _lst_edge_flags( NULL), 
              _num_vertices( 0), _num_normals( 0), _dim_tex_coords( 0), _num_components( 0),
              _hay_edge_flags( GL_FALSE), _draw_texture( GL_FALSE), _use_normals( GL_FALSE) {
    _lst_elements[ 0] = _lst_elements[ 1] = _lst_elements[ 2] = _lst_elements[ 3]  = NULL;
    _num_elements[ 0] = _num_elements[ 1] = _num_elements[ 2] = _num_elements[ 3]  = 0;
    
    // _mode[ 0] = GL_POINTS;
    // _mode[ 1] = GL_LINES;
    // _mode[ 2] = GL_TRIANGLES;
    // _mode[ 3] = GL_QUADS;
    
    _id_vbo_vertices = _id_vbo_normals = _id_vbo_tex_coords = _id_vbo_colors = _id_vbo_edge_flags = 0;
    _id_vbo_elements[ 0] = _id_vbo_elements[ 1] = _id_vbo_elements[ 2] = _id_vbo_elements[ 3] = 0;
  };
  ~OglArray() {
    deleteVALists();
  }

  GLboolean getDrawTexture( ) const { return _draw_texture;}
  void setDrawTexture( GLboolean b) { _draw_texture = b;}

  _t_ogl_render_method getRenderMethod() const { return _render_method;}
  void setRenderMethod( _t_ogl_render_method rm);
  
  // para el modo Vertex Arrray: OpenGL 1.1
  void setVertexLists( _t_ogl_vector *points, _t_ogl_vector *normals, GLsizei num_points,
                       GLfloat *tex_coords = NULL, GLint dim_tex = 0);
  void setVertexLists( GLfloat *points, GLfloat *normals, GLsizei num_points,
                       GLfloat *tex_coords = NULL, GLint dim_tex = 0);
  void setVertexTexCoords( GLfloat *tex_coords, GLint dim_tex);
  void setVertexColors( GLfloat *colors, int num_comps); // num_comps = 3 or 4
  void setVertexNormals( _t_ogl_vector *normals, GLsizei num_normals);
  void setVertexNormals( GLfloat *normals, GLsizei num_normals);
  void setVertexEdgeFlags( GLboolean *edge_flags);
  void setPointList( GLuint *conectivity, GLsizei num_elements) {
    setElementList( 0, conectivity, num_elements);
  }
  void setLineList( GLuint *conectivity, GLsizei num_elements) {
    setElementList( 1, conectivity, num_elements);
  }
  void setTriangleList( GLuint *conectivity, GLsizei num_elements) {
    setElementList( 2, conectivity, num_elements);
  }
  void setQuadList( GLuint *conectivity, GLsizei num_elements) {
    setElementList( 3, conectivity, num_elements);
  }
  void deleteAllVertexLists();
  void allocVertexList( GLsizei num_vertices);
  void allocTexCoordList( GLsizei num_vertices, GLint dim_tex);
  void allocColorsList( GLsizei num_vertices, GLint num_comps);
  void allocEdgeFlagsList( GLsizei num_vertices);
  void deleteVALists( );
   // OGLARRAY_POINTS | OGLARRAY_LINES | OGLARRAY_TRIANGLES | OGLARRAY_QUADS
  void renderVALists( int what_to_draw, bool use_color = false);

  // // para el modo Vertex Buffer Object: OpenGL 1.5
  // void createVBO( );
  // void deleteVBO( );
  // void renderVBO( );

  GLuint getNumPoints() const { return _num_elements[ 0];}
  GLuint getNumLines() const { return _num_elements[ 1];}
  GLuint getNumTriangles() const { return _num_elements[ 2];}
  GLuint getNumQuads() const { return _num_elements[ 3];}
  
  bool useVBO() const { return _render_method == OGLARRAY_BUFFER_OBJECT;}
  bool useVA() const { return _render_method == OGLARRAY_VERTEX_ARRAY;}
  bool useDL() const { return _render_method == OGLARRAY_DISPLAY_LIST;}
  
 private:
  static GLboolean check_VBO();
  void setElementList( int idx, GLuint *conectivity, GLsizei num_elements);

  static GLboolean _init, _hayVBO;
  _t_ogl_render_method _render_method;
  // allocs en, como minimo 8-bytes boundary
  // para NVIDIA/ATI recomiendan 32-bytes o 64-bytes por vertice
  _t_ogl_vector *_lst_vertices, *_lst_normals;
  GLfloat *_lst_tex_coords, *_lst_colors; // pueden ser ( s) o ( s, t)
  GLboolean *_lst_edge_flags;
  GLsizei _num_vertices, _num_normals;
  GLint _dim_tex_coords; // dim_tex_coords: 1 (cfill), 2(2d tex), 3, 4;
  GLint _num_components;
  GLboolean _hay_edge_flags;
  GLboolean _draw_texture, _use_normals;
  GLuint *_lst_elements[ 4]; // points, lines, triangles, quads
  GLsizei _num_elements[ 4];
  // GLenum _mode[ 4];

  GLuint _id_vbo_vertices, _id_vbo_normals, _id_vbo_tex_coords, _id_vbo_colors, _id_vbo_edge_flags;
  GLuint _id_vbo_elements[ 4]; // also used to store the Display List id of the different elements
};

// GIVEN size and align
// RETURNS in ptr --> the allocated pointer = original @ of a block of ( size + align) size
//         int aligned_ptr --> @ aligned to the next 'align' boundary.
//                             will be between ptr and ptr + align - 1
inline void *aligned_malloc( size_t size, size_t align) {
  assert( ( align & ( align - 1)) == 0); // align must be power of 2
  void *ret_ptr = NULL;
#ifdef _WIN32
  ret_ptr = _aligned_malloc( size, align);
#elif __MACH__
  ret_ptr = malloc( size); // on Mac OS X is aligned to 16 bytes boundary....
#else // no _Win32 and no Mac OS X
  int err = posix_memalign( &ret_ptr, align, size);
  if ( err) {
    ret_ptr = malloc( size); // on Mac OS X is aligned to 16 bytes boundary....
  }
#endif // _WIN32
  return ret_ptr;
}

inline void aligned_free( void *ptr) {
#ifdef _WIN32
  _aligned_free( ptr);
#elif __MACH__
  free( ptr);
#else // no _Win32 and no Mac OS X
  free( ptr);
#endif // _WIN32
}

#endif // _OGL_ARRAYS_H_
